/*
 * TODO language 
 * Copyright (c) 2010 TODO Team
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <iostream>
#include <sstream>
#include "typeman_fwd.h"
#include "types/numeric.h"

#define NUMERIC_VAL_OP(OP) \
	if (!value->type->is_child_of(TypeManager::numerictype)) \
		throw todo::Exception("cannot operate on non numeric object."); \
\
	Value* val = value->type->copy(value);\
	Value* arg = ARG(0);\
	bool mustfree = false;\
\
	const std::string& val_type = value->type->get_name();\
	const std::string& arg_type = arg->type->get_name();\
\
	if (value->type == TypeManager::inttype) {\
		if (arg->type == TypeManager::doubletype) {\
			arg = CALL_METHOD(ARG(0), "toInt", NULL);\
			mustfree = true;\
		}\
		INT_V(val) = INT_V(val) OP INT_V(arg);\
	}\
	else if (val->type == TypeManager::doubletype) {\
		if (arg->type == TypeManager::inttype) {\
			arg = CALL_METHOD(ARG(0), "toDouble", NULL);\
			mustfree = true;\
		}\
		DOUBLE_V(val) = DOUBLE_V(val) OP DOUBLE_V(arg);\
	}\
\
	if (mustfree && arg) {\
		arg->type->dealloc(arg);\
	}\
\
	return val

#define NUMERIC_VAL_INT_OP(OP) \
	if (!value->type->is_child_of(TypeManager::numerictype)) \
		throw todo::Exception("cannot operate on non numeric object."); \
\
	Value* val = value->type->copy(value);\
	Value* arg = ARG(0);\
	bool mustfree = false;\
\
	const std::string& val_type = value->type->get_name();\
	const std::string& arg_type = arg->type->get_name();\
\
	if (arg->type == TypeManager::doubletype) {\
		arg = CALL_METHOD(ARG(0), "toInt", NULL);\
		mustfree = true;\
	}\
\
	if (val->type == TypeManager::doubletype) {\
		val->type->dealloc(val); \
		val = CALL_METHOD(value, "toInt", NULL);\
	}\
	INT_V(val) = INT_V(val) OP INT_V(arg);\
\
	if (mustfree && arg) {\
		arg->type->dealloc(arg);\
	}\
\
	return val

#define NUMERIC_VAL_LOGIC_OP(OP) \
	Value *ret = NEW("bool"); \
	Value *arg = ARG(0); \
	const std::string& val_type = value->type->get_name(); \
	const Type* arg_type = ARG(0)->type; \
\
	bool mustfree = false; \
 \
	if (value->type == TypeManager::inttype) {\
		if (arg_type == TypeManager::doubletype) {\
			arg = CALL_METHOD(arg, "toInt", NULL);\
			mustfree = true;\
		}\
\
		BOOL_V(ret) = INT_V(value) OP INT_V(arg);\
	} else if (value->type == TypeManager::doubletype) {\
		if (arg_type == TypeManager::inttype) {\
			arg = CALL_METHOD(arg, "toDouble", NULL);\
			mustfree = true;\
		}\
\
		BOOL_V(ret) = DOUBLE_V(value) OP DOUBLE_V(arg);\
	}\
		\
	if (mustfree)\
		arg->type->dealloc(arg);\
\
	return ret


namespace todo { namespace type {

Numeric::Numeric() : Type("numeric", TypeManager::objecttype, true)
{
}

void Numeric::load(void)
{
	register_method(TODO_PUBLIC, "__plus__"     , this    , (MethodPtr)&Numeric::plus       , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__minus__"    , this    , (MethodPtr)&Numeric::minus      , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__div__"      , this    , (MethodPtr)&Numeric::div        , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__mult__"     , this    , (MethodPtr)&Numeric::mult       , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__mod__"      , this    , (MethodPtr)&Numeric::mod        , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__equal__"    , this    , (MethodPtr)&Numeric::equal      , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__not_equal__", this    , (MethodPtr)&Numeric::notEqual   , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__ge__"       , this    , (MethodPtr)&Numeric::greater    , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__le__"       , this    , (MethodPtr)&Numeric::lower      , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__gt__"       , this    , (MethodPtr)&Numeric::greaterThan, 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__lt__"       , this    , (MethodPtr)&Numeric::lowerThan  , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__sr__"       , this    , (MethodPtr)&Numeric::shiftRight , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__sl__"       , this    , (MethodPtr)&Numeric::shiftLeft  , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__bw_or__"    , this    , (MethodPtr)&Numeric::bitwiseOr  , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__bw_and__"   , this    , (MethodPtr)&Numeric::bitwiseAnd , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__xor__"      , this    , (MethodPtr)&Numeric::bitwiseXor , 1, type_list(1, "__this__"));
	register_method(TODO_PUBLIC, "__pre_inc__"  , this    , (MethodPtr)&Numeric::preIncrement);
	register_method(TODO_PUBLIC, "__pre_dec__"  , this    , (MethodPtr)&Numeric::preDecrement);
	register_method(TODO_PUBLIC, "__pos_inc__"  , this    , (MethodPtr)&Numeric::posIncrement);
	register_method(TODO_PUBLIC, "__pos_dec__"  , this    , (MethodPtr)&Numeric::posDecrement);
	register_method(TODO_PUBLIC, "toInt"        , "int"   , (MethodPtr)&Numeric::toInt       );
	register_method(TODO_PUBLIC, "toDouble"     , "double", (MethodPtr)&Numeric::toDouble    );
}

TODO_METHOD(Numeric, plus)
{
	NUMERIC_VAL_OP(+);
}

TODO_METHOD(Numeric, minus)
{
	NUMERIC_VAL_OP(-);
}

TODO_METHOD(Numeric, div)
{
	NUMERIC_VAL_OP(/);
}

TODO_METHOD(Numeric, mult)
{
	NUMERIC_VAL_OP(*);
}

TODO_METHOD(Numeric, mod)
{
	NUMERIC_VAL_INT_OP(%);
}

TODO_METHOD(Numeric, equal)
{
	NUMERIC_VAL_LOGIC_OP(==);
}

TODO_METHOD(Numeric, notEqual)
{
	NUMERIC_VAL_LOGIC_OP(!=);
}

TODO_METHOD(Numeric, greater)
{
	NUMERIC_VAL_LOGIC_OP(>=);
}

TODO_METHOD(Numeric, lower)
{
	NUMERIC_VAL_LOGIC_OP(<=);
}

TODO_METHOD(Numeric, greaterThan)
{
	NUMERIC_VAL_LOGIC_OP(>);
}

TODO_METHOD(Numeric, lowerThan)
{
	NUMERIC_VAL_LOGIC_OP(<);
}

TODO_METHOD(Numeric, shiftLeft)
{
	NUMERIC_VAL_INT_OP(<<);
}

TODO_METHOD(Numeric, shiftRight)
{
	NUMERIC_VAL_INT_OP(>>);
}

TODO_METHOD(Numeric, bitwiseOr)
{
	NUMERIC_VAL_INT_OP(|);
}

TODO_METHOD(Numeric, bitwiseAnd)
{
	NUMERIC_VAL_INT_OP(&);
}

TODO_METHOD(Numeric, bitwiseXor)
{
	NUMERIC_VAL_INT_OP(^);
}

TODO_METHOD(Numeric, preIncrement)
{
	const Type* val_type = value->type;

	if (val_type == TypeManager::inttype) {
		++INT_V(value);
	} else if (val_type == TypeManager::doubletype) {
		++DOUBLE_V(value);
	}

	return value->type->copy(value);
}

TODO_METHOD(Numeric, preDecrement)
{
	const Type* val_type = value->type;

	if (val_type == TypeManager::inttype) {
		--INT_V(value);
	} else if (val_type == TypeManager::doubletype) {
		--DOUBLE_V(value);
	}

	return value->type->copy(value);
}

TODO_METHOD(Numeric, posIncrement)
{
	const Type* val_type = value->type;
	Value* val = value->type->copy(value);

	if (val_type == TypeManager::inttype) {
		++INT_V(value);
	} else if (val_type == TypeManager::doubletype) {
		++DOUBLE_V(value);
	}

	return val;
}

TODO_METHOD(Numeric, posDecrement)
{
	const Type* val_type = value->type;
	Value* val = value->type->copy(value);

	if (val_type == TypeManager::inttype) {
		--INT_V(value);
	} else if (val_type == TypeManager::doubletype) {
		--DOUBLE_V(value);
	}

	return val;
}

TODO_METHOD(Numeric, toInt)
{
	if (value->type == TypeManager::doubletype) {
		Value* ret = NEW("int");
		INT_V(ret) = int(DOUBLE_V(value));
		return ret;
	}

	return copy(value); // integer.
}

TODO_METHOD(Numeric, toDouble)
{
	if (value->type == TypeManager::inttype) {
		Value* ret = NEW("double");
		DOUBLE_V(ret) = double(INT_V(value));
		return ret;
	}

	return copy(value); // double.
}

TODO_METHOD(Numeric, toString)
{
	return CALL_METHOD(value, "toString", NULL);
}

Value* Numeric::copy(const Value* value)
{
	return value->type->copy(value);
}

Value* Numeric::alloc()
{
	return NULL;
}

void Numeric::dealloc(Value* value)
{
}

}}
